package com.lagou.edu.factory;

import java.lang.reflect.Field;
import java.util.HashMap;
import java.util.HashSet;
import java.util.Set;

import org.apache.commons.collections4.CollectionUtils;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

import com.lagou.edu.anno.Autowired;
import com.lagou.edu.anno.Service;
import com.lagou.edu.anno.Transactional;
import com.lagou.edu.utils.ClassUtil;
import com.lagou.edu.utils.ConfigurationUtils;
import com.mysql.cj.util.StringUtils;

/**
 * @author 应癫
 *
 * 工厂类，生产对象（使用反射技术）
 */
public class BeanFactory {

    public static final Logger log = LoggerFactory.getLogger(BeanFactory.class);

    private static boolean loaded = false;

    /**
     * ID和CLASS的映射关系
     */
    private static final HashMap<String,Class> ID_TO_CLASS = new HashMap<>();

    /**
     * MAP容器
     */
    private static final HashMap<Class,Object> MAP = new HashMap<>();

    /**
     * 加载bean
     */
    public static void loadBean() {
        // 重复加载，直接返回
        if(loaded){
            log.warn("Container has loaded!");
            return;
        }
        // 1.读取配置文件获取要扫描的包
        ConfigurationUtils configurationUtils = new ConfigurationUtils();
        String basePackage = configurationUtils.getPropertiesByKey("ioc.scan.basePackage");

        // 2.扫描包下所有的类，添加到set中
        Set<Class<?>> classSet = ClassUtil.extracPackageClass(basePackage);
        if(CollectionUtils.isEmpty(classSet)){
            log.warn("extract nohting from basePackage:" + basePackage);
            return;
        }

        // 3.实例化拥有Service注解的类
        for (Class<?> clazz : classSet) {
            if(clazz.isAnnotationPresent(Service.class)){
                String id = clazz.getAnnotation(Service.class).value();
                getInstance(clazz, id);
            }
        }
        loaded = true;

    }

    /**
     * 获取bean并加入容器。
     * @param clazz bean的class类型
     * @param id bean的id
     * @return bean
     */
    private static Object getInstance(Class<?> clazz, String id) {
        // 先从MAP容器中获取bean，如果获取到直接返回
        // 如果id不为空则根据id获取，否则根据class获取。
        Object bean = StringUtils.isNullOrEmpty(id) ? getByClass(clazz) : getBean(id);
        if(bean != null){
            return bean;
        }

        // 利用反射创建实例
        bean = ClassUtil.newInstance(clazz);

        // 判断属性是否拥有Autowired注解
        for (Field field : clazz.getDeclaredFields()) {
            Autowired anno = field.getAnnotation(Autowired.class);
            if(anno != null){
                //如果有Autowired注解则递归调用getInstance获取bean，然后利用反射设置属性
                Object fieldValue = getInstance(field.getType(), anno.value());
                ClassUtil.setField(bean, field, fieldValue);
            }
        }

        // 判断class是否有Transactional注解，如果有则封装为拥有事务控制功能的代理对象
        if(clazz.getAnnotation(Transactional.class) != null){
            ProxyFactory proxyFactory = (ProxyFactory) getInstance(ProxyFactory.class);
            bean = proxyFactory.getProxy(bean);
        }
        MAP.put(clazz, bean);
        if (!StringUtils.isNullOrEmpty(id)) {
            ID_TO_CLASS.put(id, clazz);
        }
        return bean;
    }

    private static Object getInstance(Class<?> clazz) {
        return getInstance(clazz, null);
    }

    // 对外提供获取实例对象的接口（根据id获取）
    public static  Object getBean(String id) {
        if(ID_TO_CLASS.get(id) == null){
            return null;
        }
        return MAP.get(ID_TO_CLASS.get(id));
    }

    // 对外提供获取实例对象的接口（根据class获取）
    public static  Object getBean(Class clazz) {
        return MAP.get(clazz);
    }

    public static Set<Class> getClasses(){
        return MAP.keySet();
    }

    /**
     * 通过接口或者父类获取实现类或子类的Class集合，不包括其本身
     * @param interfaceOrClass 接口Class或者父类Class
     * @return
     */
    public static Set<Class<?>> getClassesBySuper(Class<?> interfaceOrClass) {
        //1.获取beanMap的所有class对象
        Set<Class> classes = getClasses();

        //2.判断keySet里的元素是否是传入的接口或者类的子类，如果是，就将其添加到classSet里
        Set<Class<?>> resultSet = new HashSet<>();
        classes.forEach(clazz -> {
            //判断keySet里的元素是否是传入的接口或者类的子类
            if(interfaceOrClass.isAssignableFrom(clazz) && !interfaceOrClass.equals(clazz)){
                resultSet.add(clazz);
            }
        });
        return resultSet;
    }

    private static Object getByClass(Class<?> clazz) {
        Object result = getBean(clazz);
        if(result != null){
            return result;
        }
        Set<Class<?>> classSet = getClassesBySuper(clazz);
        if(classSet.size() == 1){
            return getBean(classSet.iterator().next());
        } else if(classSet.size() > 1){
            throw new RuntimeException("multiple implemented classes for " + clazz.getName() + " please set @Autowired's value to pick one");
        }
        return null;
    }
}
